package de.unikoblenz.west.csxpoi.server;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.http.HTTPRepository;

/**
 * REST interface for categories.
 */
public class RestCategories extends HttpServlet {

	private static final long serialVersionUID = 1L;

	/**
	 * Handles GET requests.
	 */
	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		if (ServletHelper.isCollectionRequest(request)) {

			try {
				List<CategoryWrapper> categories = new ArrayList<CategoryWrapper>();

				Repository repository = new HTTPRepository(
						Constants.SESAME_SERVER, Constants.REPOSITORY_ID);

				try {
					repository.initialize();
					RepositoryConnection connection = repository
							.getConnection();

					try {
						TupleQuery query = connection
								.prepareTupleQuery(
										QueryLanguage.SPARQL,
										"PREFIX base: <"
												+ Constants.NS_BASE
												+ "> "
												+ "PREFIX rdfs: <"
												+ Constants.NS_RDFS
												+ "> "
												+ "SELECT ?category ?name ?contribution WHERE {"
												+ " ?category rdfs:subClassOf base:Poi ;"
												+ "  rdfs:label ?name ."
												+ " OPTIONAL {"
												+ "  ?contribution a base:CategoryModification ;"
												+ "   base:affectsCategory ?category ."
												+ " }"
												+ " OPTIONAL {"
												+ "  ?contribution a base:CategoryRemoval ;"
												+ "   base:affectsCategory ?category ."
												+ " }"
												+ " FILTER (?category != base:Poi)"
												+ "} ORDER BY ASC(?name)");
						TupleQueryResult result = query.evaluate();
						try {
							while (result.hasNext()) {
								BindingSet bindingSet = result.next();
								if (bindingSet.hasBinding("contribution")) {
									CategoryWrapper category = CategoryWrapper
											.createFromUri(
													bindingSet.getValue(
															"category")
															.stringValue(),
													connection);
									if (category != null
											&& !categories.contains(category)) {
										categories.add(category);
									}
								} else {
									categories.add(new CategoryWrapper(
											bindingSet.getValue("category")
													.stringValue(), bindingSet
													.getValue("name")
													.stringValue()));
								}

							}

						} finally {
							result.close();
						}
					} finally {
						connection.close();
					}

					XmlWriter.writeCategoriesResponse(response, categories);

				} catch (RepositoryException e) {
					throw new ServerException();
				} catch (MalformedQueryException e) {
					throw new ServerException();
				} catch (QueryEvaluationException e) {
					throw new ServerException();
				}

			} catch (ServerException e) {
				XmlWriter.writeCategoriesResponse(response, null);
			}

		} else { // ServletHelper.isMemberRequest(request)

			try {
				String id = ServletHelper.resolveStringParameter(request, "id");

				CategoryWrapper category = null;

				Repository repository = new HTTPRepository(
						Constants.SESAME_SERVER, Constants.REPOSITORY_ID);

				try {
					repository.initialize();
					RepositoryConnection connection = repository
							.getConnection();

					try {
						category = CategoryWrapper
								.createFromUri(id, connection);
					} finally {
						connection.close();
					}

					XmlWriter.writeCategoryResponse(response, category);

				} catch (RepositoryException e) {
					throw new ServerException();
				} catch (MalformedQueryException e) {
					throw new ServerException();
				} catch (QueryEvaluationException e) {
					throw new ServerException();
				}

			} catch (ServerException e) {
				XmlWriter.writeCategoryResponse(response, null);
			}
		}
	}

	/**
	 * Handle POST requests.
	 */
	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		if (ServletHelper.isCollectionRequest(request)) {

			try {
				String name = ServletHelper.resolveStringParameter(request,
						"name");
				String user = ServletHelper.resolveStringParameter(request,
						"user");
				List<String> superclasses = ServletHelper
						.resolveStringListParameter(request, "superclass");
				List<String> subclasses = ServletHelper
						.resolveStringListParameter(request, "subclass");
				List<String> equivalents = ServletHelper
						.resolveStringListParameter(request, "equivalent");

				Repository repository = new HTTPRepository(
						Constants.SESAME_SERVER, Constants.REPOSITORY_ID);

				try {
					repository.initialize();
					RepositoryConnection connection = repository
							.getConnection();

					CategoryWrapper category = null;

					try {
						String uri = Constants.NS_VOC
								+ URLEncoder.encode(name, "UTF-8");
						category = new CategoryWrapper(uri, name);
						for (String superclassUri : superclasses) {
							category.addSuperclass(superclassUri, connection);
						}
						for (String subclassUri : subclasses) {
							category.addSubclass(subclassUri, connection);
						}
						for (String equivalentUri : equivalents) {
							category.addEquivalent(equivalentUri, connection);
						}

						ValueFactory valueFactory = connection
								.getValueFactory();

						String contributionId = OntologyHelper
								.generateContributionUri(connection);

						if (contributionId == null)
							throw new ServerException();

						URI uriUri = valueFactory.createURI(category.getUri());
						URI contributionUri = valueFactory
								.createURI(contributionId);
						URI rdfTypeUri = valueFactory
								.createURI(Constants.URI_RDF_TYPE);
						URI categoryCreationUri = valueFactory
								.createURI(Constants.URI_BASE_CATEGORY_CREATION);
						URI affectsUri = valueFactory
								.createURI(Constants.URI_BASE_AFFECTS_CATEGORY);
						URI issuedAtUri = valueFactory
								.createURI(Constants.URI_BASE_ISSUED_AT);
						URI dateTimeUri = valueFactory
								.createURI(Constants.URI_XSD_DATE_TIME);
						Literal dateTimeLiteral = valueFactory.createLiteral(
								OntologyHelper.generateDateTimeString(),
								dateTimeUri);
						URI userUri = valueFactory.createURI(user);
						URI issuedByUri = valueFactory
								.createURI(Constants.URI_BASE_ISSUED_BY);
						URI stringUri = valueFactory
								.createURI(Constants.URI_XSD_STRING);
						URI labelUri = valueFactory
								.createURI(Constants.URI_RDFS_LABEL);
						Literal nameLiteral = valueFactory.createLiteral(
								category.getName(), stringUri);
						URI poiUri = valueFactory
								.createURI(Constants.URI_BASE_POI);
						URI subClassOfUri = valueFactory
								.createURI(Constants.URI_RDFS_SUB_CLASS_OF);
						URI sameAsUri = valueFactory
								.createURI(Constants.URI_OWL_SAME_AS);

						connection.add(contributionUri, rdfTypeUri,
								categoryCreationUri);
						connection.add(contributionUri, affectsUri, uriUri);
						connection.add(contributionUri, issuedAtUri,
								dateTimeLiteral);
						connection.add(contributionUri, issuedByUri, userUri);
						connection.add(uriUri, subClassOfUri, poiUri);
						connection.add(uriUri, labelUri, nameLiteral);

						for (CategoryWrapper superclass : category
								.getSuperclasses()) {
							URI superclassUri = valueFactory
									.createURI(superclass.getUri());
							connection
									.add(uriUri, subClassOfUri, superclassUri);
						}
						for (CategoryWrapper subclass : category
								.getSubclasses()) {
							URI subclassUri = valueFactory.createURI(subclass
									.getUri());
							connection.add(subclassUri, subClassOfUri, uriUri);
						}
						for (CategoryWrapper equivalent : category
								.getEquivalents()) {
							URI equivalentUri = valueFactory
									.createURI(equivalent.getUri());
							connection.add(uriUri, sameAsUri, equivalentUri);
						}
					} finally {
						connection.close();
					}

					XmlWriter.writeCategoryResponse(response, category);

				} catch (RepositoryException e) {
					throw new ServerException();
				} catch (MalformedQueryException e) {
					throw new ServerException();
				} catch (QueryEvaluationException e) {
					throw new ServerException();
				}
			} catch (ServerException e) {
				XmlWriter.writeCategoryResponse(response, null);
			}
		} else { // ServletHelper.isMemberRequest(request)
			// unused
		}
	}

	/**
	 * Handles PUT requests.
	 */
	@Override
	protected void doPut(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		if (ServletHelper.isMemberRequest(request)) {

			try {
				CategoryWrapper category = null;

				if (request.getParameter("changes") != null
						|| request.getParameter("adds") != null
						|| request.getParameter("deletes") != null) {

					Repository repository = new HTTPRepository(
							Constants.SESAME_SERVER, Constants.REPOSITORY_ID);

					try {
						repository.initialize();
						RepositoryConnection connection = repository
								.getConnection();

						try {
							String id = ServletHelper.resolveStringParameter(
									request, "id");
							category = CategoryWrapper.createFromUri(id,
									connection);
							if (category == null)
								throw new ServerException();

							ValueFactory valueFactory = connection
									.getValueFactory();

							String user = ServletHelper.resolveStringParameter(
									request, "user");
							String value = ServletHelper
									.resolveStringParameter(request, "value");

							URI categoryUri = valueFactory.createURI(category
									.getUri());
							URI userUri = valueFactory.createURI(user);
							String contributionId = OntologyHelper
									.generateContributionUri(connection);
							URI contributionUri = valueFactory
									.createURI(contributionId);
							URI dateTimeUri = valueFactory
									.createURI(Constants.URI_XSD_DATE_TIME);
							Literal dateTimeLiteral = valueFactory
									.createLiteral(OntologyHelper
											.generateDateTimeString(),
											dateTimeUri);
							URI rdfTypeUri = valueFactory
									.createURI(Constants.URI_RDF_TYPE);
							URI categoryModificationUri = valueFactory
									.createURI(Constants.URI_BASE_CATEGORY_MODIFICATION);
							URI affectsCategoryUri = valueFactory
									.createURI(Constants.URI_BASE_AFFECTS_CATEGORY);
							URI issuedAtUri = valueFactory
									.createURI(Constants.URI_BASE_ISSUED_AT);
							URI issuedByUri = valueFactory
									.createURI(Constants.URI_BASE_ISSUED_BY);
							URI categoryPropertyValueUri = valueFactory
									.createURI(Constants.URI_BASE_CATEGORY_PROPERTY_VALUE);

							connection.add(contributionUri, rdfTypeUri,
									categoryModificationUri);
							connection.add(contributionUri, affectsCategoryUri,
									categoryUri);
							connection.add(contributionUri, issuedAtUri,
									dateTimeLiteral);
							connection.add(contributionUri, issuedByUri,
									userUri);

							if (request.getParameter("changes") != null) {
								String changedProperty = ServletHelper
										.resolveStringParameter(request,
												"changes");
								URI changedPropertyUri = valueFactory
										.createURI(changedProperty);
								URI changesCategoryPropertyUri = valueFactory
										.createURI(Constants.URI_BASE_CHANGES_CATEGORY_PROPERTY);
								URI stringUri = valueFactory
										.createURI(Constants.URI_XSD_STRING);
								Literal valueLiteral = valueFactory
										.createLiteral(value, stringUri);
								connection.add(contributionUri,
										changesCategoryPropertyUri,
										changedPropertyUri);
								connection.add(contributionUri,
										categoryPropertyValueUri, valueLiteral);
							} else if (request.getParameter("adds") != null) {
								String addedProperty = ServletHelper
										.resolveStringParameter(request, "adds");
								URI addedPropertyUri = valueFactory
										.createURI(addedProperty);
								URI addsCategoryPropertyUri = valueFactory
										.createURI(Constants.URI_BASE_ADDS_CATEGORY_PROPERTY);
								connection.add(contributionUri,
										addsCategoryPropertyUri,
										addedPropertyUri);
								URI valueUri = valueFactory.createURI(value);
								connection.add(contributionUri,
										categoryPropertyValueUri, valueUri);
							} else if (request.getParameter("deletes") != null) {
								String deletedProperty = ServletHelper
										.resolveStringParameter(request,
												"deletes");
								URI deletedPropertyUri = valueFactory
										.createURI(deletedProperty);
								URI deletesCategoryPropertyUri = valueFactory
										.createURI(Constants.URI_BASE_DELETES_CATEGORY_PROPERTY);
								connection.add(contributionUri,
										deletesCategoryPropertyUri,
										deletedPropertyUri);
								URI valueUri = valueFactory.createURI(value);
								connection.add(contributionUri,
										categoryPropertyValueUri, valueUri);
							}

							category = CategoryWrapper.createFromUri(
									category.getUri(), connection);

						} finally {
							connection.close();
						}

						XmlWriter.writeCategoryResponse(response, category);

					} catch (RepositoryException e) {
						throw new ServerException();
					} catch (MalformedQueryException e) {
						throw new ServerException();
					} catch (QueryEvaluationException e) {
						throw new ServerException();
					}
				}
			} catch (ServerException e) {
				XmlWriter.writeCategoryResponse(response, null);
			}

		} else { // ServletHelper.isCollectionRequest(request)
			// unused
		}
	}

	/**
	 * Handles DELETE requests.
	 */
	@Override
	protected void doDelete(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		if (ServletHelper.isMemberRequest(request)) {

			try {
				CategoryWrapper category = null;

				Repository repository = new HTTPRepository(
						Constants.SESAME_SERVER, Constants.REPOSITORY_ID);

				try {
					repository.initialize();
					RepositoryConnection connection = repository
							.getConnection();

					try {
						String id = ServletHelper.resolveStringParameter(
								request, "id");
						category = CategoryWrapper
								.createFromUri(id, connection);
						if (category == null)
							throw new ServerException();

						ValueFactory valueFactory = connection
								.getValueFactory();

						String user = ServletHelper.resolveStringParameter(
								request, "user");

						URI categoryUri = valueFactory.createURI(category
								.getUri());
						URI userUri = valueFactory.createURI(user);
						String contributionId = OntologyHelper
								.generateContributionUri(connection);
						URI contributionUri = valueFactory
								.createURI(contributionId);
						URI rdfTypeUri = valueFactory
								.createURI(Constants.URI_RDF_TYPE);
						URI categoryRemovalUri = valueFactory
								.createURI(Constants.URI_BASE_CATEGORY_REMOVAL);
						URI affectsCategoryUri = valueFactory
								.createURI(Constants.URI_BASE_AFFECTS_CATEGORY);
						URI issuedAtUri = valueFactory
								.createURI(Constants.URI_BASE_ISSUED_AT);
						URI dateTimeUri = valueFactory
								.createURI(Constants.URI_XSD_DATE_TIME);
						Literal dateTimeLiteral = valueFactory.createLiteral(
								OntologyHelper.generateDateTimeString(),
								dateTimeUri);
						URI issuedByUri = valueFactory
								.createURI(Constants.URI_BASE_ISSUED_BY);

						connection.add(contributionUri, rdfTypeUri,
								categoryRemovalUri);
						connection.add(contributionUri, affectsCategoryUri,
								categoryUri);
						connection.add(contributionUri, issuedAtUri,
								dateTimeLiteral);
						connection.add(contributionUri, issuedByUri, userUri);

					} finally {
						connection.close();
					}

					XmlWriter.writeCategoryResponse(response, category);

				} catch (RepositoryException e) {
					throw new ServerException();
				} catch (MalformedQueryException e) {
					throw new ServerException();
				} catch (QueryEvaluationException e) {
					throw new ServerException();
				}

			} catch (ServerException e) {
				XmlWriter.writeCategoryResponse(response, null);
			}
		} else { // ServletHelper.isCollectionRequest(request)
			// unused
		}
	}

}
